/*
 * Copyright (c) 2014. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package com.dc.gameserver.extComponents.utilsKit.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

public class PackageUtils {
    private static final String PROTOCOL_FILE = "file";
    private static final String PROTOCOL_JAR = "jar";

    private static final String PREFIX_FILE = "file:";

    private static final String JAR_URL_SEPERATOR = "!/";
    private static final String CLASS_FILE = ".class";	//class文件的后缀名
	public final static List<String> EMPTY_LIST = new ArrayList<String>(0);

    private static final Logger LOGGER = LoggerFactory.getLogger(PackageUtils.class);
	/**
	 * 查找指定package下的class
	 * @param packageName
	 *               package名字,允许正则表达式
	 * @return
	 */
	public static String[] findClassesInPackage(String packageName) {
		return findClassesInPackage(packageName,EMPTY_LIST,EMPTY_LIST);
	}
	/**
	 * 查找指定package下的class
	 * @param packageName 
	 * 			package名字,允许正则表达式
	 * @param included
	 * 			包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param excluded
	 * 		            不包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @return
	 * 	                符合要求的classes,全路径名
	 */
	public static String[] findClassesInPackage(String packageName,List<String> included,List<String> excluded) {
		String packageOnly = packageName;
		boolean recursive = false;
		if(packageName.endsWith(".*")) { //递归判断
			packageOnly = packageName.substring(0,packageName.lastIndexOf(".*"));
			recursive = true;
		}
		List<String> vResult = new ArrayList<String>();
		String packageDirName = packageOnly.replace(".", "/");
		try {
			Enumeration<URL> dirs = ClassLoader.getSystemClassLoader().getResources(packageDirName);
			URL url = null;
			String protocol = null;
			while(dirs.hasMoreElements()) {
				url = dirs.nextElement();
				protocol = url.getProtocol();
				if("file".equals(protocol)) { //如果是目录结构
					findClassesInDirPackage(packageOnly,included,excluded, URLDecoder.decode(url.getFile(), "UTF-8"),recursive,vResult);
				} else if("jar".equals(protocol)) {//如果是jar结构 
					JarFile jar = ((JarURLConnection)url.openConnection()).getJarFile();
					Enumeration<JarEntry> entries = jar.entries();
					JarEntry entry = null;
					while(entries.hasMoreElements()) {
						entry = entries.nextElement();
						String name = entry.getName();
						if(name.charAt(0) == '/') {
							name = name.substring(1);
						}
						if(name.startsWith(packageDirName)) {
							int idx = name.lastIndexOf("/");
							if(idx != -1) {
								packageName = name.substring(0,idx).replace('/', '.');
							}
							if(idx != -1 || recursive) {
								if(name.endsWith(".class") && !entry.isDirectory()) {
									String className = name.substring(packageName.length() + 1,name.length() -6);
									includeOrExcludeClass(packageName,className,included,excluded,vResult);
								}
							}
						}
					}
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return vResult.toArray(new String[vResult.size()]);
		
	}
	/**
	 * 通过遍历目录的方式查找符合要求的包下的class文件
	 * @param packageName
	 *               包名,确定名
	 * @param included
	 *               包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param excluded
	 *               不包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param packagePath
	 *               包目录路径
	 * @param recursive
	 *               是否递归
	 * @param classes
	 *               结果集
	 */
	private static void findClassesInDirPackage(String packageName,List<String> included,List<String> excluded,String packagePath,final boolean recursive,List<String> classes) {
		File dir = new File(packagePath);
		if(!dir.exists() || !dir.isDirectory()) {
			return;
		}
		//过滤目录和以class后缀的文件
		File[] dirFiles = dir.listFiles(new FileFilter() {

			@Override
			public boolean accept(File pathname) {
				return (recursive && pathname.isDirectory()) || (pathname.getName().endsWith(".class"));
			}
			
		});
		for(File file : dirFiles) {
			if(file.isDirectory()) {
				//递归处理
				findClassesInDirPackage(packageName + "." + file.getName() ,included,excluded,file.getAbsolutePath(),recursive,classes);
			} else {
				String className = file.getName().substring(0,file.getName().length() - 6);
				includeOrExcludeClass(packageName,className,included,excluded,classes);
			}
		}
	}
	/**
	 * 过滤
	 * @param packageName
	 * 					包名,确定名
	 * @param className
	 * 					短类名,不包含package前缀,确定名
	 * @param included
	 * 					包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param excluded
	 * 					不包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param classes
	 * 					结果集
	 */
	private static void includeOrExcludeClass(String packageName,String className,List<String> included,List<String> excluded,List<String> classes) {
		if(isIncluded(className,included,excluded)) {
			classes.add(packageName + '.' + className);
		}
	}
	
	/**
	 * 是否包含
	 * @param name 
	 * 				短类名,不包含package前缀,确定名
	 * @param included
	 *              包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @param excluded
	 * 	                               不包含的类名(短类名,不包含package前缀,允许正则表达式)
	 * @return include-true,else-false
	 */
	private static boolean isIncluded(String name,List<String> included,List<String> excluded) {
		boolean result = false;
		if(included.size() == 0 && excluded.size() == 0) {
			return true;
		} else {
			boolean isIncluded = find(name,included);
			boolean isExcluded = find(name,excluded);
			if(isIncluded && isExcluded) {
				result = true;
			} else if(isExcluded) {
				result = false;
			} else {
				result = included.size() == 0;
			}
		}
		return result;
	}
	/**
	 * 
	 * @param name
	 * 				短类名,不包含package前缀
	 * @param list
	 * @return
	 */
	private static boolean find(String name,List<String> list) {
		for(String regexpStr : list) {
			if(Pattern.matches(regexpStr, name)) {
				return true;
			}
		}
		return false;
	}



    /**
     * 返回某个包下的所有的类
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {
        // Assert.notNull(packageName,"packageName can not be null for PackageUtils.getClasses");
        //包名不能为空弄个
        if (packageName == null) {
            LOGGER.error("packageName can not be null");
            return null;
        }
        //返回类列表
        List<Class<?>> list = new ArrayList<Class<?>>();
        Enumeration<URL> en = null;
        try {
            en = PackageUtils.class.getClassLoader().getResources(
                    nameToPath(packageName));
            while (en.hasMoreElements()) {
                URL url = en.nextElement();
                //class文件的方式
                if (PROTOCOL_FILE.equals(url.getProtocol())) {
                    String path = url.getFile();
                    path = URLDecoder.decode(path, "utf-8");	//url解码
                    File root = new File(path);
                    findInDirectory(list, root, root, packageName);
                }
                //JAR的方式
                else if (PROTOCOL_JAR.equals(url.getProtocol())){
                    findInJar(list, getJarFile(url), packageName);
                }
            }
        } catch (IOException e) {
            LOGGER.error("无效的包名:" + packageName, e.getCause());
        }
        return list;
    }

    /**
     * 返回某个包下特定父类或接口的所有子类
     * @param <T>
     * @param packageName	包名
     * @param clazz		父类
     * @return
     */
    @SuppressWarnings("unchecked")
    public  static <T extends Object> List<Class<T>> getClasses(String packageName,Class<T> clazz) {
        List<Class<?>> classes = getClasses(packageName);
        List<Class<T>> returnClasses = new ArrayList<Class<T>>();
        for(Class<?> cls : classes){
            //不是脚本类，跳过
            if(!clazz.isAssignableFrom(cls)){
                continue;
            }
            //抽象类，跳过
            if(Modifier.isAbstract(cls.getModifiers())){
                continue;
            }
            returnClasses.add((Class<T>)cls);
        }
        return returnClasses;
    }

    /**
     * 返回jar文件file对象
     * @param url 文件url
     * @return jar文件的file对象
     */
    public static File getJarFile(URL url) {
        String file = url.getFile();
        if (file.startsWith(PREFIX_FILE))
            file = file.substring(PREFIX_FILE.length());
        int end = file.indexOf(JAR_URL_SEPERATOR);
        if (end != (-1))
            file = file.substring(0, end);
        return new File(file);
    }

    /**
     * 从指定的目录查找指定包的所有类，适用于.class的方式
     * @param results 指定包目录下所有的类，返回结果
     * @param rootDir	类根目录
     * @param dir	当前目录
     * @param packageName	包名
     */
    private static void findInDirectory(List<Class<?>> results, File rootDir,
                                        File dir, String packageName) {
        File[] files = dir.listFiles();	//列出目录下的文件
        if(files == null){
            return;
        }
        String rootPath = rootDir.getPath();
        //遍历
        for (File file : files)
            //是文件
            if (file.isFile()) {
                String classFileName = file.getPath();
                //后缀名为class
                if (classFileName.endsWith(CLASS_FILE)) {
                    String className = classFileName.substring(rootPath
                            .length()
                            - packageName.length(), classFileName.length()
                            - CLASS_FILE.length());
                    try {
                        Class<?> clz = Class.forName(pathToName(className));
                        results.add(clz);
                    } catch (ClassNotFoundException e) {
                        LOGGER.error("无法获取类:" + className, e.getCause()); // 该错误应该不会出现
                    }
                }
            }
            //递归此目录
            else if (file.isDirectory()){
                findInDirectory(results, rootDir, file, packageName);
            }
    }

    /**
     * 从指定的JAR文件中查找指定包的所有的类，适用于jar的方式
     * @param results
     * @param file
     * @param packageName
     */
    private static void findInJar(List<Class<?>> results, File file,
                                  String packageName) {
        JarFile jarFile = null;
        String packagePath = nameToPath(packageName) + "/";
        try {
            jarFile = new JarFile(file);
            Enumeration<JarEntry> en = jarFile.entries();
            while (en.hasMoreElements()) {
                JarEntry je = en.nextElement();
                String name = je.getName();
                if (name.startsWith(packagePath) && name.endsWith(CLASS_FILE)) {
                    String className = name.substring(0, name.length()
                            - CLASS_FILE.length());
                    try {
                        Class<?> clz = Class.forName(pathToName(className));
                        results.add(clz);
                    } catch (ClassNotFoundException e) {
                        LOGGER.error("无法获取类:" + className, e.getCause()); // 该错误应该不会出现
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("无法读取 Jar 文件:" + file.getName(), e);
        } finally {
            if (jarFile != null)
                try {
                    jarFile.close();
                } catch (IOException e) {
                }
        }
    }

    /**
     * 将类名的字符串形式转换为路径表现形式
     *
     * @param className	类名
     * @return 路径的字符串
     */
    private static String nameToPath(String className) {
        return className.replace('.', '/');
    }

    /**
     * 将路径转换为类的字符串表现形式
     *
     * @param path 路径
     * @return 类名的字符串形式
     */
    private static String pathToName(String path) {
        return path.replace('/', '.').replace('\\', '.');
    }

    public static List<Method> getSortedMethodList(Class<?> clazz) {
        List<Method> methods = Arrays.asList(clazz.getDeclaredMethods());
        Collections.sort(methods, new Comparator<Method>() {
            public int compare(Method m1, Method m2) {
                String lower1 = m1.toString().toLowerCase();
                String lower2 = m2.toString().toLowerCase();
                return lower1.compareTo(lower2);
            }
        });

        return methods;
    }



}